package com.centerm.bluetooth.ibridge;

import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Message;

import com.centerm.bluetooth.ibridge.BluetoothIBridgeAdapter.MyHandler;
import com.centerm.bluetooth.ibridge.constants.ChannelConfig;
import com.centerm.bluetooth.ibridge.constants.ConnectStatus;
import com.centerm.bluetooth.ibridge.listener.DataReceiver;
import com.centerm.util.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

final class Connections {
    private MyHandler mHandler;
    private ArrayList<DataReceiver> mDataReceivers;
    private ConnectionList mList;
    private Object LOCK = new Object();

    public Connections(MyHandler handler) {
        mHandler = handler;
        mList = new ConnectionList();
        mList.clear();
    }

    void registerDataReceiver(DataReceiver receiver) {
        if (mDataReceivers == null) {
            synchronized (LOCK) {
                if (mDataReceivers == null)
                    mDataReceivers = new ArrayList<>();
            }
        }
        synchronized (LOCK) {
            if (!mDataReceivers.contains(receiver)) {
                mDataReceivers.add(receiver);
            }
        }
    }

    void unregisterDataReceiver(DataReceiver receiver) {
        synchronized (LOCK) {
            if (mDataReceivers == null) {
                return;
            }
            mDataReceivers.remove(receiver);
        }
    }

    void disconnect(BluetoothIBridgeDevice device) {
        ConnectionThread found = mList.foundDevice(device);
        BluetoothIBridgeAdapter.log("try to release connection:" + found);
        if (found == null) {
            BluetoothIBridgeAdapter.log("The device[" + device + "] may has been closed.");
            return;
        }
        if (device != null) {
            device.setConnectStatus(ConnectStatus.STATUS_DISCONNECTTING);
        }
        found.cancel();
    }

    void write(BluetoothIBridgeDevice device, byte[] buffer, int length) {
        mList.write(device, buffer, length);
    }

    List<BluetoothIBridgeDevice> getCurrentConnectedDevice() {
        synchronized (LOCK) {
            return mList.getCurrentConnectedDevice();
        }
    }

    void connected(BluetoothSocket socket, BluetoothIBridgeDevice device) {
        BluetoothIBridgeAdapter.log("connected:" + device + "socket:" + socket);

        ConnectionThread conn = new ConnectionThread(socket, device, mHandler, mDataReceivers);
        conn.start();
        mList.addConnection(conn);

        if (device != null) {
            device.connected(true);
            device.setConnectStatus(ConnectStatus.STATUS_CONNECTED);
        }
        // Send the name of the connected device back to the UI Activity

        Message msg = mHandler.obtainMessage(BluetoothIBridgeAdapter.MESSAGE_DEVICE_CONNECTED);
        msg.obj = device;
        mHandler.sendMessage(msg);
        BluetoothIBridgeAdapter.log("connected, after send message.");
    }

    /**
     * The class maintains a list of connected devices.Each element of list is a
     * device/socket pair.It supports add,remove connected devices and require
     * for a device(or some type) whether has connected.
     */
    private class ConnectionList {

        private List<ConnectionThread> mConnectedDevices = new ArrayList<ConnectionThread>();
        private byte[] LOCK = new byte[0];

        public void write(BluetoothIBridgeDevice device, byte[] buffer, int length) {
            BluetoothIBridgeAdapter.log("write data in Connections:" + length);
            if (null == device || !device.isConnected() || null == buffer || length <= 0) {
                return;
            }
            ConnectionThread found = foundDevice(device);
            if (null != found) {
                found.write(buffer, length);
            }
        }

        public void addConnection(ConnectionThread connection) {
            synchronized (LOCK) {
                BluetoothIBridgeDevice device = connection.getDevice();
                for (final ConnectionThread ds : mConnectedDevices) {
                    if (device.equals(ds.getDevice())) {
                        mConnectedDevices.remove(ds);
                        break;
                    }
                }
                mConnectedDevices.add(connection);
            }
        }

        private ConnectionThread foundDevice(BluetoothIBridgeDevice device) {
            ConnectionThread found = null;
            synchronized (LOCK) {
                for (final ConnectionThread ds : mConnectedDevices) {
                    if (device.equals(ds.getDevice())) {
                        found = ds;
                        break;
                    }
                }
            }
            return found;
        }

        public void clear() {
            synchronized (LOCK) {
                mConnectedDevices.clear();
            }
        }

        public List<BluetoothIBridgeDevice> getCurrentConnectedDevice() {
            List<BluetoothIBridgeDevice> devicesList = new ArrayList<BluetoothIBridgeDevice>();
            synchronized (LOCK) {
                for (final ConnectionThread ds : mConnectedDevices) {
                    BluetoothIBridgeDevice device = ds.getDevice();
                    if (device != null && !devicesList.contains(device)) {
                        devicesList.add(device);
                    }
                }
            }
            return devicesList;
        }

        public void releaseAllConnections() {
            synchronized (LOCK) {
                for (final ConnectionThread ds : mConnectedDevices) {
                    if (ds != null) {
                        ds.cancel();
                    }
                }
                mConnectedDevices.clear();
            }
        }
    }

    void disconnectAll() {
        mList.releaseAllConnections();
    }
}

/**
 * This thread runs during a connection with a remote device. It handles all
 * incoming and outgoing transmissions.
 */
class ConnectionThread extends Thread {
    private static final String TAG = "IBridgeConnectedThread";
    private final BluetoothSocket mmSocket;
    private final BluetoothIBridgeDevice mmDevice;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    private final MyHandler mHandler;
    private ArrayList<DataReceiver> mDataReceivers;

    private static final int ONCE_MAXLEN = 1024;
    private static final int MAXLEN = ONCE_MAXLEN * 64;
    private byte[] buffer;
    private boolean isSocketReset = false;

    public ConnectionThread(BluetoothSocket socket, BluetoothIBridgeDevice device, MyHandler handler,
                            ArrayList<DataReceiver> receivers) {
        Logger.d(TAG, "create ConnectedThread: " + device.getDeviceName());
        mmSocket = socket;
        mmDevice = device;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;

        mHandler = handler;
        mDataReceivers = receivers;
        buffer = new byte[MAXLEN];

        // Get the BluetoothSocket input and output streams
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
            Logger.e(TAG, e, "temp sockets not created");
        }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
        isSocketReset = false;
    }

    public void run() {
        Logger.i(TAG, "BEGIN mConnectedThread BEGIN READ");
        // Keep listening to the InputStream while connected
        while (true) {
            try {
                // Read from the InputStream
                byte[] buffer = ChannelConfig.read(mmInStream);
                if (buffer != null && buffer.length > 0) {
                    mmDevice.buffer = buffer;
                    mmDevice.length = buffer.length;
                    Logger.i(TAG, " read len == " + mmDevice.length);
                    // Send the obtained bytes to the UI Activity
                    if (mDataReceivers != null) {
                        @SuppressWarnings("unchecked")
                        ArrayList<DataReceiver> listenersCopy = (ArrayList<DataReceiver>) mDataReceivers.clone();
                        int numListeners = listenersCopy.size();
                        for (int i = 0; i < numListeners; ++i) {
                            DataReceiver er = listenersCopy.get(i);
                            if (mmDevice.isValidDevice() && er != null) {
                                er.onDataReceived(mmDevice, mmDevice.buffer, mmDevice.length);
                            }
                        }
                    }
                }
            } catch (IOException e) {
                Logger.i(TAG, e, "disconnected");
                connectionLost(e.getMessage());
                break;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Indicate that the connection was lost and notify the UI Activity.
     */
    private void connectionLost(String exceptionMsg) {
        if (!isSocketReset) {
            resetSocket(mmSocket);
        }

        // Send a failure message back to the Activity
        if (mmDevice != null) {
            mmDevice.connected(false);
            mmDevice.setConnectStatus(ConnectStatus.STATUS_DISCONNECTED);
        }

        Message msg = mHandler.obtainMessage(BluetoothIBridgeAdapter.MESSAGE_DEVICE_DISCONNECTED);
        msg.obj = mmDevice;

        Bundle bundle = new Bundle();
        bundle.putString(MyHandler.BUNDLE_EXCEPTION, exceptionMsg);
        msg.setData(bundle);

        mHandler.sendMessage(msg);
    }

    /**
     * Write to the connected OutStream.
     *
     * @param buf The bytes to write
     */
    public void write(final byte[] buf, final int length) {
        try {
            int len = Math.min(length, ONCE_MAXLEN);
            System.arraycopy(buf, 0, buffer, 0, len);
            BluetoothIBridgeAdapter.log("write data in Connections's ConnectionThread:" + length);
            mmOutStream.write(buffer, 0, length);
            mmOutStream.flush();
            BluetoothIBridgeAdapter.log("write data in Connections's ConnectionThread:" + length + " suc");
        } catch (Exception e) {
            Message msg = mHandler.obtainMessage(BluetoothIBridgeAdapter.MESSAGE_WRITE_FAILED);
            msg.obj = mmDevice;

            Bundle bundle = new Bundle();
            bundle.putString(MyHandler.BUNDLE_EXCEPTION, e.getMessage());
            msg.setData(bundle);

            mHandler.sendMessage(msg);
            BluetoothIBridgeAdapter.log("Exception during write");
            Logger.e(TAG, e, "Exception during write");
        }
    }

    public void cancel() {
        isSocketReset = true;
        resetSocket(mmSocket);
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }

        if (!(o instanceof ConnectionThread)) {
            return false;
        }

        ConnectionThread conn = (ConnectionThread) o;
        return conn.mmDevice.equals(mmDevice);
    }

    public BluetoothIBridgeDevice getDevice() {
        return mmDevice;
    }

    static void resetSocket(BluetoothSocket sock) {
        if (null != sock) {
            try {
                InputStream is = sock.getInputStream();
                if (null != is) {
                    is.close();
                }
            } catch (IOException ie) {
                ie.printStackTrace();
            }

            try {
                OutputStream os = sock.getOutputStream();
                if (null != os) {
                    os.close();
                }
            } catch (IOException oe) {
                oe.printStackTrace();
            }

            try {
                sock.close();
            } catch (IOException se) {
                se.printStackTrace();
            }
        }
    }
}
